package megrez.boot.autoconfigure.minio;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.util.StringUtils;

import io.minio.ComposeObjectArgs;
import io.minio.ComposeSource;
import io.minio.CopyObjectArgs;
import io.minio.CopySource;
import io.minio.DeleteObjectTagsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetObjectTagsArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.SetObjectTagsArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import io.minio.messages.Tags;
import lombok.RequiredArgsConstructor;

/**
 * MinIO对象操作服务
 * 
 * @author Lucky Yang
 * @since 2.6.9
 */
@RequiredArgsConstructor
public class MinioObjectClient {
    private final MinioClient client;

    /**
     * 上传对象流
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param stream     流
     * @param consumer   builder消费
     * @return
     */
    public ObjectWriteResponse putObject(String bucketName, String objectName, InputStream stream,
            Consumer<PutObjectArgs.Builder> consumer) {
        try {
            PutObjectArgs.Builder builder = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(stream, stream.available(), -1);

            if (consumer != null) {
                consumer.accept(builder);
            }

            return client.putObject(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 上传对象流
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param stream     流
     * @return
     */
    public ObjectWriteResponse putObject(String bucketName, String objectName, InputStream stream) {
        return putObject(bucketName, objectName, stream, null);
    }

    /**
     * 获取对象流
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param consumer   builder消费
     * @return
     */
    public GetObjectResponse getObject(String bucketName, String objectName, Consumer<GetObjectArgs.Builder> consumer) {
        try {
            GetObjectArgs.Builder builder = GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName);

            if (consumer != null) {
                consumer.accept(builder);
            }
            return client.getObject(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 获取对象流
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @return
     */
    public GetObjectResponse getObject(String bucketName, String objectName) {
        return getObject(bucketName, objectName, null);
    }

    /**
     * 获取对象流-支持端点续传
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param offset     起始字节的位置
     * @param length     要读取的长度 （可选，如果无值则代表读到对象结尾）
     * @return
     */
    public GetObjectResponse getObject(String bucketName, String objectName, Long offset, Long length) {
        return getObject(bucketName, objectName, builder -> {
            builder.offset(offset);
            if (length != null) {
                builder.length(length);
            }
        });
    }

    /**
     * 获取对象信息和对象的元数据
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param consumer   builder消费
     * @return
     */
    public StatObjectResponse statObject(String bucketName, String objectName,
            Consumer<StatObjectArgs.Builder> consumer) {
        try {
            StatObjectArgs.Builder builder = StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName);

            if (consumer != null) {
                consumer.accept(builder);
            }
            return client.statObject(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 获取对象信息和对象的元数据
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @return
     */
    public StatObjectResponse statObject(String bucketName, String objectName) {
        return statObject(bucketName, objectName, null);
    }

    /**
     * 对象是否存在
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @return
     */
    public boolean existObject(String bucketName, String objectName) {
        try {
            StatObjectResponse response = statObject(bucketName, objectName, null);
            return response != null;
        } catch (MinioRuntimeException e) {
            return false;
        }
    }

    /**
     * 获取对象的元数据
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @return
     */
    public Map<String, String> getObjectUserMetadata(String bucketName, String objectName) {
        return statObject(bucketName, objectName).userMetadata();
    }

    /**
     * 获取预签名对象网址, 默认Method.GET方法，分享链接地址失效时间为7天
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param consumer   bilder消费
     * @return
     */
    public String getPresignedObjectUrl(String bucketName, String objectName,
            Consumer<GetPresignedObjectUrlArgs.Builder> consumer) {
        try {
            GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .method(Method.GET);
            if (consumer != null) {
                consumer.accept(builder);
            }
            return client.getPresignedObjectUrl(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param method     方法， GET, HEAD, POST, PUT, DELETE;
     * @param expiry     失效时间（以秒为单位），默认是7天，不得大于七天
     * @return
     */
    public String getPresignedObjectUrl(String bucketName, String objectName, Method method, Duration expiry) {
        return getPresignedObjectUrl(bucketName, objectName, builder -> {
            builder.method(method);
            if (expiry != null) {
                builder.expiry((int) expiry.toSeconds());
            }
        });
    }

    /**
     * 根据对象前缀查询对象
     * 
     * @param bucketName 桶
     * @param consumer   builder消费
     * @return
     */
    public List<Item> listObjects(String bucketName, Consumer<ListObjectsArgs.Builder> consumer) {
        try {
            ListObjectsArgs.Builder builder = ListObjectsArgs.builder()
                    .bucket(bucketName);
            if (consumer != null) {
                consumer.accept(builder);
            }
            Iterable<Result<Item>> iterable = client.listObjects(builder.build());
            return StreamSupport
                    .stream(iterable.spliterator(), true)
                    .map(itemResult -> {
                        try {
                            return itemResult.get();
                        } catch (InvalidKeyException | ErrorResponseException | IllegalArgumentException
                                | InsufficientDataException | InternalException | InvalidResponseException
                                | NoSuchAlgorithmException | ServerException | XmlParserException | IOException e) {
                            throw new MinioRuntimeException(e);
                        }
                    })
                    .collect(Collectors.toList());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 
     * 根据对象前缀查询对象
     * 
     * @param bucketName 桶
     * @param prefix     桶中对象的前缀 默认 空字符串
     * @param recursive  是否递归子目录
     * @return
     */
    public List<Item> listObjects(String bucketName, String prefix, Boolean recursive) {
        return listObjects(bucketName, builder -> {
            if (StringUtils.hasText(prefix)) {
                builder.prefix(prefix);
            }
            if (recursive != null) {
                builder.recursive(recursive);
            }
        });
    }

    /**
     * 组合源对象列表, 通过使用服务器端副本组合来自不同源对象的数据来创建对象，服务器上已存在的对象列表再次组合成一个对象
     * 
     * @param bucketName       桶
     * @param objectName       对象
     * @param sourceObjectList 组合组合的源对象列表
     * @param consumer         builder消费
     * @return
     */
    public ObjectWriteResponse composeObject(String bucketName, String objectName, List<ComposeSource> sourceObjectList,
            Consumer<ComposeObjectArgs.Builder> consumer) {
        try {
            ComposeObjectArgs.Builder builder = ComposeObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .sources(sourceObjectList);
            if (consumer != null) {
                consumer.accept(builder);
            }
            return client.composeObject(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 
     * 组合源对象列表, 通过使用服务器端副本组合来自不同源对象的数据来创建对象，服务器上已存在的对象列表再次组合成一个对象
     * 
     * @param bucketName       桶
     * @param objectName       对象
     * @param sourceObjectList 组合组合的源对象列表
     * @return
     */
    public ObjectWriteResponse composeObject(String bucketName, String objectName,
            List<ComposeSource> sourceObjectList) {
        return composeObject(bucketName, objectName, sourceObjectList, null);
    }

    /**
     * 复制对象
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param source     源对象
     * @param consumer   builder消费
     * @return
     */
    public ObjectWriteResponse copyObject(String bucketName, String objectName, CopySource source,
            Consumer<CopyObjectArgs.Builder> consumer) {
        try {
            CopyObjectArgs.Builder builder = CopyObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .source(source);
            if (consumer != null) {
                consumer.accept(builder);
            }
            return client.copyObject(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 
     * 复制对象
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param source     源对象
     * @return
     */
    public ObjectWriteResponse copyObject(String bucketName, String objectName, CopySource source) {
        return copyObject(bucketName, objectName, source, null);
    }

    /**
     * 设置对象的标签
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param tags       标签
     * @param consumer   builder消费
     */
    public void setObjectTags(String bucketName, String objectName, Tags tags,
            Consumer<SetObjectTagsArgs.Builder> consumer) {
        try {
            SetObjectTagsArgs.Builder builder = SetObjectTagsArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .tags(tags);
            if (consumer != null) {
                consumer.accept(builder);
            }
            client.setObjectTags(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 设置对象的标签
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param tags       标签
     */
    public void setObjectTags(String bucketName, String objectName, Tags tags) {
        setObjectTags(bucketName, objectName, tags, null);
    }

    /**
     * 获取对象的标签
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param consumer   builder消费
     * @return
     */
    public Tags getObjectTags(String bucketName, String objectName, Consumer<GetObjectTagsArgs.Builder> consumer) {
        try {
            GetObjectTagsArgs.Builder builder = GetObjectTagsArgs.builder()
                    .bucket(bucketName)
                    .object(objectName);
            if (consumer != null) {
                consumer.accept(builder);
            }
            return client.getObjectTags(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 获取对象的标签
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @return
     */
    public Tags getObjectTags(String bucketName, String objectName) {
        return getObjectTags(bucketName, objectName, null);
    }

    /**
     * 删除对象标签
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param consumer   builder消费
     */
    public void deleteObjectTags(String bucketName, String objectName,
            Consumer<DeleteObjectTagsArgs.Builder> consumer) {
        try {
            DeleteObjectTagsArgs.Builder builder = DeleteObjectTagsArgs.builder()
                    .bucket(bucketName)
                    .object(objectName);
            if (consumer != null) {
                consumer.accept(builder);
            }
            client.deleteObjectTags(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 删除对象标签
     * 
     * @param bucketName 桶
     * @param objectName 对象
     */
    public void deleteObjectTags(String bucketName, String objectName) {
        deleteObjectTags(bucketName, objectName, null);
    }

    /**
     * 删除单个对象
     * 
     * @param bucketName 桶
     * @param objectName 对象
     * @param consumer   builder消费
     */
    public void deleteObject(String bucketName, String objectName, Consumer<RemoveObjectArgs.Builder> consumer) {
        try {
            RemoveObjectArgs.Builder builder = RemoveObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName);
            if (consumer != null) {
                consumer.accept(builder);
            }
            client.removeObject(builder.build());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 
     * 删除单个对象
     * 
     * @param bucketName 桶
     * @param objectName 对象
     */
    public void deleteObject(String bucketName, String objectName) {
        deleteObject(bucketName, objectName, null);
    }

    /**
     * 删除多个对象
     * 
     * @param bucketName  桶
     * @param objectNames 对象列表
     * @param consumer    builder消费
     */
    public List<DeleteError> deleteObjects(String bucketName, List<DeleteObject> objectNames,
            Consumer<RemoveObjectsArgs.Builder> consumer) {
        try {
            RemoveObjectsArgs.Builder builder = RemoveObjectsArgs.builder()
                    .bucket(bucketName)
                    .objects(objectNames);
            if (consumer != null) {
                consumer.accept(builder);
            }
            Iterable<Result<DeleteError>> iterable = client.removeObjects(builder.build());
            return StreamSupport.stream(iterable.spliterator(), true).map(result -> {
                try {
                    return result.get();
                } catch (InvalidKeyException | ErrorResponseException | IllegalArgumentException
                        | InsufficientDataException | InternalException | InvalidResponseException
                        | NoSuchAlgorithmException | ServerException | XmlParserException | IOException e) {
                    throw new MinioRuntimeException(e);
                }
            }).collect(Collectors.toList());
        } catch (Exception e) {
            throw new MinioRuntimeException(e);
        }
    }

    /**
     * 删除多个对象
     * 
     * @param bucketName  桶
     * @param objectNames 对象列表
     * @return
     */
    public List<DeleteError> deleteObjects(String bucketName, List<DeleteObject> objectNames) {
        return deleteObjects(bucketName, objectNames, null);
    }
}
